package rjeutsky.ui;

import java.sql.SQLException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import rjeutsky.ApplicationController;
import rjeutsky.models.Publication;
import rjeutsky.models.Subscription;
import rjeutsky.models.User;
import rjeutsky.ui.models.ItemPrinter;
import rjeutsky.ui.models.MenuAction;
import rjeutsky.ui.models.MenuItem;
import rjeutsky.validators.interfaces.Validator;

public class ConsoleUi {
	protected ConsoleMenu mainMenu;
	protected ConsoleMenu startMenu;
	protected ApplicationController controller;
	
	public ApplicationController getController() {
		return controller;
	}

	public void setController(ApplicationController controller) {
		this.controller = controller;
	}

	protected User currentUser;
	
	protected List<MenuItem> startScreenMenuItems;
	protected List<MenuItem> adminMenuItems;
	protected List<MenuItem> commonMenuItems;
	
	public ConsoleUi(){
		initMenuItems();
		startMenu = new ConsoleMenu(startScreenMenuItems);
	}
	
	public void Run() throws SQLException{
		while (true) {
			startMenu.printMenu();
			startMenu.processMenu();
			if (currentUser != null) {
				mainMenu.printMenu();
				mainMenu.processMenu();
			}
			else {
				controller.finishApplication();
				return;
			}
		}
	}
	
	protected void login() {
		StringConsoleRequest request = new StringConsoleRequest();
		request.setRequestText("login");
		request.Run();
		String login = request.getValue();
		request.setRequestText("password");
		request.Run();
		String password = request.getValue();
		currentUser = controller.getUser(login, password);
		if (currentUser != null) {
			boolean isAdmin = controller.checkIsAdmin(currentUser);
			initMainMenu(isAdmin);
		}
	}
	
	protected void newUser() {
		StringConsoleRequest request = new StringConsoleRequest();
		request.setRequestText("Enter user login");
		request.Run();
		String login = request.getValue();
		request.setRequestText("Enter user password");
		request.Run();
		String password = request.getValue();
		User user = new User();
		user.setLogin(login);
		user.setPassword(password);
		Validator<User> validator = controller.getUserValidator();
		if (!validator.validate(user)) {
			System.out.println("User creation failed");
			System.out.println(validator.getValidationErrorMessage());
		}
		else {
			controller.createUser(user);
			System.out.println("The user has been created successfully");
		}
	}
	
	protected void newPublication() {
		StringConsoleRequest nameRequest = new StringConsoleRequest();
		nameRequest.setRequestText("Enter publication title");
		nameRequest.Run();
		String name = nameRequest.getValue();
		LongConsoleRequest priceRequest = new LongConsoleRequest();
		priceRequest.setRequestText("Enter publication price");
		priceRequest.Run();
		long price = priceRequest.getValue();
		Publication publication = new Publication();
		publication.setName(name);
		publication.setPrice(price);
		Validator<Publication> validator = controller.getPublicationValidator();
		if (!validator.validate(publication)) {
			System.out.println("Publication creation failed");
			System.out.println(validator.getValidationErrorMessage());
		}
		else {
			controller.createPublication(publication);
			System.out.println("The publication has been created successfully");
		}
	}
	
	protected void editPublication() {
		List<Publication> publications = controller.getPublications();
		if (publications.size() > 0) {
			ConsoleSelectorRequest<Publication> publicationRequest = new ConsoleSelectorRequest<Publication>();
			publicationRequest.setOptions(publications);
			publicationRequest.setRequestText("Select a publication");
			publicationRequest.Run();
			Publication selectedPublication = publicationRequest.value;
			StringConsoleRequest nameRequest = new StringConsoleRequest();
			nameRequest.setRequestText(String.format("Enter publication title (was %1$s)", selectedPublication.getName()));
			nameRequest.Run();
			String name = nameRequest.getValue();
			LongConsoleRequest priceRequest = new LongConsoleRequest();
			priceRequest.setRequestText(String.format("Enter publication price (was %1$d)", selectedPublication.getPrice()));
			priceRequest.Run();
			long price = priceRequest.getValue();
			selectedPublication.setName(name);
			selectedPublication.setPrice(price);
			Validator<Publication> validator = controller.getPublicationValidator();
			if (!validator.validate(selectedPublication)) {
				System.out.println("Publication update failed");
				System.out.println(validator.getValidationErrorMessage());
			}
			else {
				try {
					controller.updatePublication(selectedPublication);
					System.out.println("The publication has been updated successfully");
				} catch (SQLException e) {
					controller.ReportSqlError(e);
				}
			}
		}
		else {
			System.out.println("There are no available publications");
		}
	}
	
	protected void newSubscription() {
		List<Publication> publications = controller.getPublications();
		if (publications.size() > 0) {
			PublicationSetSelectorRequest publicationSetRequest = new PublicationSetSelectorRequest();
			publicationSetRequest.setRequestText("Select available publications");
			publicationSetRequest.setOptions(publications);
			publicationSetRequest.Run();
			Set<Long> selectedPublicationIds = publicationSetRequest.getValue();
			Subscription subscription = new Subscription();
			subscription.setUserId(currentUser.getId());
			Validator<Subscription> validator = controller.getSubscriptionValidator();
			if (validator.validate(subscription)) {
				if (selectedPublicationIds.size() > 0) {
					try {
						long predictedSum = controller.GetPaymentSum(selectedPublicationIds);
						BooleanConsoleRequest confirmRequest = new BooleanConsoleRequest();
						confirmRequest.setRequestText(String.format("Total payment sum is %1$d. Do you want to proceed?", predictedSum));
						confirmRequest.Run();
						if (confirmRequest.getValue()) {
							controller.createSubscription(subscription, selectedPublicationIds);
							System.out.println("Subscription was successfully created");
						}
					} catch (SQLException e ) {
						controller.ReportSqlError(e);
					}
				} 
				else {
					System.out.println("Subscription was not created. No publication were selected");
				}
			}
			else {
				System.out.println("Subscription creation failed");
				System.out.println(validator.getValidationErrorMessage());
			}
		}
		else {
			System.out.println("There are no available publications");
		}
	}
	
	protected void makePayment() {
		List<Subscription> unpayedSubscriptions = controller.getUnpayedSubscriptions();
		if (unpayedSubscriptions.size() > 0) {
			ConsoleSelectorRequest<Subscription> subscriptionRequest = new ConsoleSelectorRequest<Subscription>();
			subscriptionRequest.setPrinter(new ItemPrinter<Subscription>() {
				
				@Override
				public String print(Subscription item) {
					return printSubscription(item);
				}
			});
			subscriptionRequest.setOptions(unpayedSubscriptions);
			subscriptionRequest.setRequestText("Please select subscription to pay");
			subscriptionRequest.Run();
			Subscription selectedSubscription = subscriptionRequest.getValue();
			System.out.println(String.format("Reader: %1$s", controller.getUser(selectedSubscription.getUserId()).getLogin()));
			for (Publication publication : controller.getSubscriptionPublications(selectedSubscription)) {
				System.out.println(publication);
			}
			try {
				System.out.println(String.format("Total sum is %1$d", controller.GetPaymentSum(selectedSubscription)));
				BooleanConsoleRequest confirmRequest = new BooleanConsoleRequest();
				confirmRequest.setRequestText("Do you want to process payment?");
				confirmRequest.Run();
				if (confirmRequest.getValue()) {
					controller.processPayment(selectedSubscription);
					System.out.println("Payment was processed");
				}
			}
			catch (SQLException ex) {
				controller.ReportSqlError(ex);
			}
		}
		else {
			System.out.println("There are no unpayed subscriptions");
		}
	}
	
	protected String printSubscription(Subscription subscription) {
		// prints subscription like admin (publication1, publication2)
		User user = controller.getUser(subscription.getUserId());
		List<Publication> publications = controller.getSubscriptionPublications(subscription);
		StringBuilder builder = new StringBuilder();
		builder.append(user.getLogin());
		builder.append(" (");
		for (Iterator<Publication> iterator = publications.iterator(); iterator.hasNext(); ) {
			Publication publication = iterator.next();
			builder.append(publication.getName());
			if (iterator.hasNext()) {
				builder.append(", ");
			}
		}
		builder.append(")");
		
		return builder.toString();
	}
	
	protected void showPublications() {
		List<Publication> publications = controller.getPublications();
		if (publications.size() > 0) {
			System.out.println("--- Publications list ---");
			for (Publication publication : publications) {
				System.out.println(publication.toString());
			}
		}
		else {
			System.out.println("There are no available publications");
		}
	}
	
	protected void showUserSubscriptions() {
		List<User> users = controller.getUsers();
		ConsoleSelectorRequest<User> userSelector = new ConsoleSelectorRequest<User>();
		userSelector.setOptions(users);
		userSelector.setRequestText("Please select user");
		userSelector.Run();
		User user = userSelector.getValue();
		List<Publication> publications = controller.getUserPublications(user);
		if (publications.size() > 0) {
			System.out.println("Publications in user's subscriptions:");
			for (Publication publication : publications) {
				System.out.println(publication);
			}
		} 
		else {
			System.out.println("There are no publications in subscriptions of this user");
		}
	}
	
	protected void initMainMenu(boolean isAdmin){
		mainMenu = new ConsoleMenu(isAdmin ? adminMenuItems : commonMenuItems);
	}
	
	protected void initMenuItems() {
		commonMenuItems = new LinkedList<MenuItem>();
		adminMenuItems = new LinkedList<MenuItem>();
		startScreenMenuItems = new LinkedList<MenuItem>();
		startScreenMenuItems.add(new MenuItem("l", "login", new MenuAction() {
			@Override
			public boolean doAction() {
				login();
				if (currentUser == null) {
					startMenu.printMenu();
				}
				return currentUser == null;
			}
		}));
		startScreenMenuItems.add(new MenuItem("q", "quit", new MenuAction() {
			@Override
			public boolean doAction() {
				// return false to exit;
				return false;
			}
		}));
		addMenuItem(new MenuItem("nu", "Add new user", new MenuAction() {
			@Override
			public boolean doAction() {
				newUser();
				return true;
			}
		}), true);
		addMenuItem(new MenuItem("np", "Add new publication", new MenuAction() {
			@Override
			public boolean doAction() {
				newPublication();
				return true;
			}
		}), true);
		addMenuItem(new MenuItem("ep", "Edit existing publication", new MenuAction() {
			@Override
			public boolean doAction() {
				editPublication();
				return true;
			}
		}), true);
		addMenuItem(new MenuItem("p", "Show unpayed subscriptions (make payment)", new MenuAction() {
			@Override
			public boolean doAction() {
				makePayment();
				return true;
			}
		}), true);
		addMenuItem(new MenuItem("r", "show publications for user", new MenuAction() {
			@Override
			public boolean doAction() {
				showUserSubscriptions();
				return true;
			}
		}), true);
		addMenuItem(new MenuItem("ns", "new subscription", new MenuAction() {
			@Override
			public boolean doAction() {
				newSubscription();
				return true;
			}
		}), false);
		addMenuItem(new MenuItem("sp", "show all publications", new MenuAction() {
			@Override
			public boolean doAction() {
				showPublications();
				return true;
			}
		}), false);
		addMenuItem(new MenuItem("h", "help", new MenuAction() {
			@Override
			public boolean doAction() {
				mainMenu.printMenu();
				return true;
			}
		}), false);
		addMenuItem(new MenuItem("q", "log out", new MenuAction() {
			@Override
			public boolean doAction() {
				currentUser = null;
				// return false to exit;
				return false;
			}
		}), false);
		
	}
	
	protected void addMenuItem(MenuItem item, boolean isAdminOnly) {
		adminMenuItems.add(item);
		if (!isAdminOnly) {
			commonMenuItems.add(item);
		}
	}
}
